#include "common.h"
#include "nemu.h"
#include "burst.h"
#include "memory/cache.h"
#include "memory/tlb.h"

uint32_t dram_read(hwaddr_t, size_t);
void dram_write(hwaddr_t, size_t, uint32_t);
int is_mmio(hwaddr_t);
uint32_t mmio_read(hwaddr_t ,size_t,int);
void mmio_write(hwaddr_t,size_t,uint32_t,int);

lnaddr_t seg_translate(swaddr_t addr,size_t len,uint8_t sreg_id){
	if(!cpu.cr0.protect_enable)
		return addr;
	else
		return cpu.sreg[sreg_id].base+addr;
}

hwaddr_t page_translate(lnaddr_t addr){
	if(cpu.cr0.protect_enable==1&&cpu.cr0.paging==1){
		uint32_t dir=addr>>22;
		uint32_t page=(addr>>12)&0x3ff;
		uint32_t Bias=addr&0xfff;

		int i=read_tlb(addr);
		if(i!=-1)
			return ((tlb[i].page_num<<12)+Bias);

		uint32_t dir_begin=cpu.cr3.page_directory_base;
		uint32_t dir_position=(dir_begin<<12)+(dir<<2);
		Page_Descriptor first,second;
		first.val=hwaddr_read(dir_position,4);
		Assert(first.P==1,"Unavaliable page");
		uint32_t page_begin=first.addr;
		uint32_t page_position=(page_begin<<12)+(page<<2);
		second.val=hwaddr_read(page_position,4);
		Assert(second.P==1,"Unavaliable page");
		uint32_t addr_begin=second.addr;
		hwaddr_t hwaddr=(addr_begin<<12)+Bias;
		write_tlb(addr,hwaddr);
		return hwaddr;
	}
	else
		return addr;
}


/* Memory accessing interfaces */

uint32_t hwaddr_read(hwaddr_t addr, size_t len) {
	int cache_f_way_1_index=cache_read_f(addr);
	uint32_t block_bias=addr&(CACHE_BLOCK_SIZE-1);
	uint8_t ret[BURST_LEN<<1];
	if(block_bias+len>CACHE_BLOCK_SIZE){
		int cache_f_way_2_index=cache_read_f(addr+CACHE_BLOCK_SIZE-block_bias);
		memcpy(ret,caf[cache_f_way_1_index].data+block_bias,CACHE_BLOCK_SIZE-block_bias);
		memcpy(ret+CACHE_BLOCK_SIZE-block_bias,caf[cache_f_way_2_index].data,len-(CACHE_BLOCK_SIZE-block_bias));
	}
	else
		memcpy(ret,caf[cache_f_way_1_index].data+block_bias,len);

	int temp=0;
	uint32_t res=unalign_rw(ret+temp,4)&(~0u>>((4-len)<<3));
	return res;
}

void hwaddr_write(hwaddr_t addr, size_t len, uint32_t data) {
	//dram_write(addr, len, data);
	int index=is_mmio(addr);
	if(index>=0) {
		mmio_write(addr,len,data,index);
		return;
	}
	cache_write_f(addr,len,data);
}

uint32_t lnaddr_read(lnaddr_t addr, size_t len) {
	assert(len==1||len==2||len==4);
	uint32_t Bias=addr&0xfff;
	if(Bias+len-1>0xfff){
		size_t n1=0xfff-Bias+1;
		size_t n2=len-n1;
		uint32_t len1=lnaddr_read(addr,n1);
		uint32_t len2=lnaddr_read(addr+n1,n2);
		uint32_t count=(len2<<(n1<<3))|len1;
		return count;
	}

	else{
                hwaddr_t hwaddr=page_translate(addr);
	        return hwaddr_read(hwaddr, len);
	}
}

void lnaddr_write(lnaddr_t addr, size_t len, uint32_t data) {
	assert(len==1||len==2||len==4);
	uint32_t Bias=addr&0xfff;
	if(Bias+len-1>0xfff){
		size_t n1=0xfff-Bias+1;
		size_t n2=len-n1;
		lnaddr_write(addr,n1,data&((1<<(n1<<3))-1));
		lnaddr_write(addr+n1,n2,data>>(n1<<3));
	}

	else{
	        hwaddr_t hwaddr=page_translate(addr);
	        hwaddr_write(hwaddr, len, data);
	}
}

uint32_t swaddr_read(swaddr_t addr, size_t len) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	lnaddr_t lnaddr=seg_translate(addr,len,current_sreg);
	return lnaddr_read(lnaddr, len);
}

void swaddr_write(swaddr_t addr, size_t len, uint32_t data) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	lnaddr_t lnaddr=seg_translate(addr,len,current_sreg);
	lnaddr_write(lnaddr, len, data);
}

